﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace WeChatSDK.Common
{
    public class WeChatXmlUtils
    {
        /// <summary>
        /// 将对象序列化为XML字符串
        /// </summary>
        /// <param name="source">需要序列化的对象</param>
        /// <returns>序列化后的XML字符串</returns>
        public static string SerializeToXml<T>(T source)
        {
            if (source != null)
            {
                MemoryStream stream = new MemoryStream();
                XmlWriter xmlWriter = XmlWriter.Create(stream, new XmlWriterSettings() { Async = true, OmitXmlDeclaration = true });

                Type type = source.GetType();
                if (type.CustomAttributes.Any(p => p.AttributeType.Equals(typeof(XmlTypeAttribute))))
                {
                    XmlTypeAttribute xmlTypeAttribute = (XmlTypeAttribute)type.GetCustomAttribute(typeof(XmlTypeAttribute));
                    xmlWriter.WriteStartElement(xmlTypeAttribute.TypeName);
                }
                else
                {
                    xmlWriter.WriteStartElement("xml");
                }
                PropertyInfo[] properties = type.GetProperties();

                foreach (PropertyInfo propertyInfo in properties)
                {
                    if (propertyInfo.CustomAttributes.Any(p => p.AttributeType.Equals(typeof(XmlElementAttribute))))
                    {
                        XmlWriter writer = XmlWriter.Create(xmlWriter);
                        XmlElementAttribute xmlElementAttribute = (XmlElementAttribute)propertyInfo.GetCustomAttribute(typeof(XmlElementAttribute));
                        writer.WriteStartElement(xmlElementAttribute.ElementName);
                        if (xmlElementAttribute.DataType.Equals("string"))
                        {
                            writer.WriteCDataAsync(propertyInfo.GetValue(source).ToString());
                        }
                        else
                        {
                            writer.WriteValue(propertyInfo.GetValue(source));
                        }
                        writer.WriteEndElement();
                    }
                    else if (propertyInfo.CustomAttributes.Any(p => p.AttributeType.Equals(typeof(XmlArrayAttribute))))
                    {
                        XmlWriter writer = XmlWriter.Create(xmlWriter);
                        XmlArrayAttribute xmlArrayAttribute = (XmlArrayAttribute)propertyInfo.GetCustomAttribute(typeof(XmlArrayAttribute));
                        writer.WriteStartElement(xmlArrayAttribute.ElementName);
                        dynamic arrayObject = propertyInfo.GetValue(source);
                        if (arrayObject != null)
                        {
                            Type arrayObjectType = arrayObject.GetType();
                            foreach (object obj in arrayObject)
                            {
                                Type objType = obj.GetType();
                                if (objType.CustomAttributes.Any(p => p.AttributeType.Equals(typeof(XmlTypeAttribute))))
                                {
                                    XmlTypeAttribute xmlTypeAttribute = (XmlTypeAttribute)objType.GetCustomAttribute(typeof(XmlTypeAttribute));
                                    writer.WriteStartElement(xmlTypeAttribute.TypeName);
                                }
                                WriteArrayItem(xmlWriter, obj, obj);
                                writer.WriteEndElementAsync();
                            }

                        }
                        writer.WriteEndElementAsync();
                    }
                }
                xmlWriter.WriteEndElementAsync();
                xmlWriter.FlushAsync();

                stream.Position = 0;
                StringBuilder sb = new StringBuilder();
                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        sb.Append(line);
                    }
                    reader.Close();
                }

                return sb.ToString();
            }
            return string.Empty;
        }
        private static void WriteArrayItem(XmlWriter xmlWriter, object obj, object source)
        {
            Type type = obj.GetType();
            PropertyInfo[] properties = type.GetProperties();

            foreach (PropertyInfo propertyInfo in properties)
            {
                if (propertyInfo.CustomAttributes.Any(p => p.AttributeType.Equals(typeof(XmlElementAttribute))))
                {
                    XmlWriter writer = XmlWriter.Create(xmlWriter);
                    XmlElementAttribute xmlElementAttribute = (XmlElementAttribute)propertyInfo.GetCustomAttribute(typeof(XmlElementAttribute));
                    writer.WriteStartElement(xmlElementAttribute.ElementName);
                    if (xmlElementAttribute.DataType.Equals("string"))
                    {
                        writer.WriteCDataAsync(propertyInfo.GetValue(source).ToString());
                    }
                    else
                    {
                        writer.WriteValue(propertyInfo.GetValue(source));
                    }
                    writer.WriteEndElementAsync();
                }
            }
        }
        /// <summary>
        /// 将XML字符串反序列化为对象
        /// </summary>
        /// <typeparam name="T">对象类型</typeparam>
        /// <param name="xml">XML字符</param>
        /// <returns></returns>
        public static T DeserializeToObject<T>(string xml)
        {
            T source;
            XmlSerializer serializer = new XmlSerializer(typeof(T));

            StringReader reader = new StringReader(xml);
            XmlTextReader xmlTextReader = new XmlTextReader(reader);
            xmlTextReader.Namespaces = false;
            source = (T)serializer.Deserialize(xmlTextReader);
            reader.Close();
            return source;
        }
    }
}
